package 
{
	import com.*;
	import com.Logging.CGSClient;
	import com.Logging.CGSClientConstants;
	import com.Logging.ClientAction;
	import com.Logging.ClientActionType;
	import com.greensock.TweenLite;
	import com.greensock.easing.Quad;
	
	import data.*;
	
	import events.GCEvent;
	import events.GCQuestEvent;
	
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.events.TimerEvent;
	import flash.sampler.NewObjectSample;
	import flash.ui.Mouse;
	import flash.utils.Dictionary;
	import flash.utils.Timer;
	
	import loading.*;
	
	import menu.GCWorldMapDisplay;
	
	import org.flixel.*;
	
	import ui.button.GCButton;
	import ui.list.ScrollList;
	import ui.popup.GCTextPopup;
	import ui.textfield.GCTextField;
		
	
	/**
	 * ...
	 * @author 
	 */
	public class GCPlayState extends FlxState
	{
		// Logging
		public static var logger:CGSClient;
		
		// Player information
		private var userID:int;
		static public var playerData:GCPlayerData;
		static private var _mapData:GCMap;
		
		// Tutorial information
		private static var _tutorialInstance:GCTutorial;
		private static var _tutorialLoadedQuests:Dictionary;
		
		// UI Components
		private var _mainDisplay:GCMainDisplay;
		private var _bodyBar:GCBodyBar;
		private static var _infoBar:GCInformationBar;
		
		// Resource collection timer
		private var _resourceGatherTimer:Timer;
		
		// Timer to check if other people bought thier items
		private var _salesCheckTimer:Timer;
		
		private static var _playStateInstance:GCPlayState;
		private static var _coveringSprite:Sprite;
		private static var _disableWaitQueue:Vector.<Sprite>;
		
		// A wait queue for giving a sales summary
		private var _salesWaitQueue:Vector.<uint>;
		
		// A list of items sold since last login
		private var _salesSinceLastLogin:Vector.<GCItem>;
		
		// Testing
		private var s:GCButton;
		private var button:GCButton;
		private var shapes:Sprite;
		private var _bottomBorder:Sprite;		
		private var _bottomBackground:Bitmap;
		
		
		/**
		 * Use this function as an entry point from the login stage
		 * 
		 * buildingsList: keyed by location, gives list of building type ids
		 * workersList: keyed by location, gives list of workers and what they are doing
		 * inventoryList: keyed by location, gives a list of GCItems at location
		 */
		public function initializeFromDatabase(playerInformation:Dictionary, shipsList:Dictionary, questsList:Dictionary, 
												buildingsList:Dictionary, workersList:Dictionary, inventoryList:Dictionary):void
		{
			if (GCConstants.DEBUG_MODE)
			{
				_mapData = new GCMap();
				playerData = new GCPlayerData();
				_tutorialLoadedQuests = new Dictionary();
				return;
			}
			
			trace("init with data from server");
			var crewLoadObj:GCCrewLoadObject;
			var item:GCItem ;
			var i:uint;
			_mapData = new GCMap();
			playerData = new GCPlayerData();
			 
			/*
			 * Iterate through each city and try to reconstruct data needed in that city.
			 */
			for (i = 0; i < GCCityData.NUM_CITIES; i++)
			{
				var constructedBuildings:Vector.<uint> = buildingsList[i];
				var crewListAt:Vector.<GCCrewLoadObject> = workersList[i];
				var inventoryAt:Vector.<GCItem> = inventoryList[i];
				
				var warehousePresent:Boolean = false;
				
				// Iterate through all constructed buildings and figure out how many
				// workers to put in them
				var gcBuildingsList:Vector.<GCCityBuilding> = new Vector.<GCCityBuilding>();
				for each (var bldgId:uint in constructedBuildings)
				{
					var building:GCCityBuilding = new GCCityBuilding(bldgId, 0);
					
					if (building.buildingId == GCBuildingData.BUILDING_WAREHOUSE)
						warehousePresent = true;
					
					if (crewListAt != null)
					{
						
						// Iterate through the crew list and see if crew tasks match up with
						// the id of a harvest building
						for each (crewLoadObj in crewListAt)
						{
							if (crewLoadObj._tid == bldgId)
							{
								building.numWorkers = crewLoadObj._amount;
								break;
							}
						}
					}
					gcBuildingsList.push(building);
				}
				
				// Iterate to calculate the total number of workers at this city
				var totalCrewAt:uint = 0;
				if (crewListAt != null)
				{
					for each (crewLoadObj in crewListAt)
					{
						totalCrewAt += crewLoadObj._amount;
					}
				}
				
				
				// Iterate through the items at this city, separate between items on sale
				// and items that are available
				var itemsAvailable:Vector.<GCItem> = new Vector.<GCItem>();
				var itemsForSale:Vector.<GCItem> = new Vector.<GCItem>();
				var capacityTaken:uint = 0;
				for each (item in inventoryAt)
				{
					(item.isSale) ? itemsForSale.push(item) : itemsAvailable.push(item);
					capacityTaken += item.quantity;
				}
				var availableInventory:GCInventory = new GCInventory(itemsAvailable, capacityTaken, GCBuildingData.WAREHOUSE_CAPACITY);
				var saleInventory:GCInventory = new GCInventory(itemsForSale, capacityTaken, GCBuildingData.WAREHOUSE_CAPACITY);
				
				if (!warehousePresent)
				{
					availableInventory = null;
					saleInventory = null;
				}
				
				_mapData.constructCity(i, gcBuildingsList, totalCrewAt, availableInventory, saleInventory);
			}
			
			/*
			 * Fill in player details
			 */
			playerData.id = playerInformation["pid"];
			playerData.totalXp = playerInformation["xp"];
			playerData.gold = playerInformation["gold"];
			playerData.setLocation(playerInformation["lid"]);
			playerData.tutorialStage = playerInformation["tutorial"];
			trace("player tutorial " + playerData.tutorialStage);
			
			// Set the level based on total experience
			//
			var accumulatedXP:uint = 0;
			for (i = 1; i < GCExperienceData._experienceSequence.length; i++)
			{
				accumulatedXP += GCExperienceData._experienceSequence[i];
				if (playerData.totalXp <= accumulatedXP)
				{
					var lastLevelAccumulated:uint = accumulatedXP - GCExperienceData._experienceSequence[i];
					playerData.playerLevel = i;
					playerData.lvlXp = playerData.totalXp - lastLevelAccumulated;
					break;
				}
			}
			
			/*
			 * Iterate through all the player's ships
			 */
			var shipCollection:Vector.<GCShip> = new Vector.<GCShip>();
			for each (var shipStruct:GCShipLoadObject in shipsList)
			{
				var ship:GCShip = new GCShip(shipStruct._sid, shipStruct._type, shipStruct._upgrades);
				shipCollection.push(ship);
				if (shipStruct._active)
				{
					playerData.setShip(ship);
				}
			}
			playerData.setShipCollection(shipCollection);
			
			/*
			 * Get number of workers who are on the ship.
			 */
			var shipLid:uint = GCCityData.CITY_SHIP;
			var crewAtShip:uint = 0;
			if (workersList[shipLid] != null)
			{
				for each (crewLoadObj in workersList[shipLid])
				{
					crewAtShip += crewLoadObj._amount;
				}
			}
			playerData.shipWorkers = crewAtShip;
			
			/*
			 * Fill in the ship's inventory
			 */
			var capacityTakenShip:uint = 0;
			for each (item in inventoryList[shipLid])
			{
				capacityTakenShip += item.quantity;
			}
			playerData.shipInventory = new GCInventory(inventoryList[shipLid] as Vector.<GCItem>, capacityTakenShip, 100);
			
			/*
			 * Fill in the quests
			 */
			_tutorialLoadedQuests = new Dictionary();
			for (var obj:Object in questsList)
			{
				var qid:uint = obj as uint;
				var quest:GCQuest = questsList[qid];
				var template:uint = quest.templateId;
				quest.qid = qid;
				
				// Add quest to the player's list
				playerData.addQuest(quest);
				
				// See which quests are tutorials so we can properly set up the tutorial
				// sequence.
				if (template >= GCQuestData.QUEST_TEMPLATE_TUTORIAL_0)
				{
					_tutorialLoadedQuests[template] = quest;
				}
			}
			
			trace("Done loading data.");
		}
		
		// TODO: Check if there is a big failure and exit in all of these callbacks.
		public static function getUid(uid:String):void { }
	
		public static function getDqid(dqid:String):void { }
		
		public static function getDqidReportNewLevel(dqid:String):void {
			logger.ReportLevel(dqid, playerData.shipLocation, getDqid);
		}
		
		override public function create():void
		{	
			// Logging
			if (GCConstants.LOGGING_DATA)
			{
				logger = new CGSClient(CGSClientConstants.URL, CGSClientConstants.SIMECO, CGSClientConstants.CURRENT_CID);
				logger.SetUid(getUid);
				logger.SetDqid(getDqidReportNewLevel);
				
				logger.LogAction(new ClientAction(ClientActionType.GAME_LOADED));
			}

			FlxState.bgColor = 0xccff33;
			
			// Create a covering sprite
			_playStateInstance = this;
			_coveringSprite = new Sprite();
			_coveringSprite.graphics.beginFill(0x000000, 0.5);
			_coveringSprite.graphics.drawRect(0, 0, FlxG.width, FlxG.height);
			_coveringSprite.graphics.endFill();
			_disableWaitQueue = new Vector.<Sprite>();
			
			/*
			 * Timer for gathering resources from a player's harvest buildings.
			 */
			_resourceGatherTimer = new Timer(GCConstants.RESOURCE_GATHER_INTERVAL);
			_resourceGatherTimer.addEventListener(TimerEvent.TIMER, onGatherResource);
			
			/*
			 * Timer to check for successful sales
			 */
			_salesCheckTimer = new Timer(GCConstants.SALES_CHECK_INTERVAL);
			_salesCheckTimer.addEventListener(TimerEvent.TIMER, onCheckPlayerSales);
			
			/*
			 * Tutorial init, must reconstruct the subgoals list from the list
			 * of quests.
			 */
			_tutorialInstance = GCTutorial.getInstance(8);//playerData.tutorialStage, _tutorialLoadedQuests);
			_tutorialInstance.addEventListener(GCEvent.QUEST_RESOLVED_EVENT, onTutorialQuestComplete);
			_tutorialInstance.addEventListener(GCEvent.ADD_NEW_QUESTS_EVENT, onAddTutorialQuests);
			
			// Create the body bar
			_bodyBar = new GCBodyBar();
			_bodyBar.x = 5
			_bodyBar.y = FlxG.height - _bodyBar.height;
			
			//Add general information
			_infoBar = new GCInformationBar(_bodyBar.x + _bodyBar.width, FlxG.width - 300);
			_infoBar.initializeData(playerData.gold, GCCityData.getNameFromId(playerData.shipLocation), playerData.playerLevel, playerData.lvlXp);
			_infoBar.x = 20; 
			_infoBar.y = FlxG.height - _bodyBar.height - _infoBar.height;
			
			// Create the main display		
			_mainDisplay = new GCMainDisplay(FlxG.width, FlxG.height - _bodyBar.height - _infoBar.height);
			
			// Add the listeners for switching the main display
			_bodyBar.addEventListener(GCEvent.OPEN_SHIP_INVENTORY_EVENT, displayInventory);
			_bodyBar.addEventListener(GCEvent.OPEN_SYNTHESIZE_EVENT, displaySynthesize);
			_bodyBar.addEventListener(GCEvent.OPEN_SHIP_REPAIR_EVENT, displayRepair);
			_bodyBar.addEventListener(GCEvent.OPEN_TROPHY_ROOM_EVENT, onDisplayTrophyRoom);
			_bodyBar.addEventListener(GCEvent.QUEST_RESOLVED_EVENT, onQuestFinished);
			_bodyBar.addEventListener(GCEvent.REFRESH_MENU_EVENT, _mainDisplay.refreshDisplay);
			_bodyBar.addEventListener("Display World Map", displayMap);
			_bodyBar.addEventListener("Display Info", displayInfo);
			
			
			// Create the image background for the bottom border
			_bottomBorder = new Sprite();
			var bit:Bitmap = new Bitmap(FlxG.addBitmap(GCResources.border));
			_bottomBorder.addChild(bit);
			_bottomBorder.x = 0;
			_bottomBorder.y = FlxG.height - bit.height;
			_bottomBorder.mouseEnabled = false;
			
			_bottomBackground = GCResources.getWoodBackground(_bottomBorder.width, _bottomBorder.height);
			_bottomBackground.x = _bottomBorder.x;
			_bottomBackground.y = _bottomBorder.y;
			
			// Add the components to the screen
			this.addChild(_mainDisplay);
			this.addChild(_bottomBackground);
			this.addChild(_bottomBorder);
			this.addChild(_infoBar);
			this.addChild(_bodyBar);
			
			
			// DEBUG- adding recipes and up
			//GCPlayState.getPlayerInventory().addToInventory(new GCItem(555566, GCItemData.ITEM_RECIPE_RUDDER, 1, playerData.getPlayerId()));
			//GCPlayState.getPlayerInventory().addToInventory(new GCItem(555567, GCItemData.ITEM_FLAG_ONE, 1, playerData.getPlayerId()));
			//GCPlayState.getPlayerInventory().addToInventory(new GCItem(555568, GCItemData.ITEM_MOTOR, 1, playerData.getPlayerId()));
			
			s = new GCButton(onButtonClick);
			s.loadGraphic(GCResources.down_arrow, 10, 10);
			s.x = 100;
			s.y = 100;			
			this.addChild(s);
			s.addEventListener(MouseEvent.MOUSE_DOWN, xm)
			s.addEventListener(MouseEvent.MOUSE_UP, ym);
			
			/*
			shapes = new Sprite();
			shapes.graphics.beginFill(0x000000);
			shapes.graphics.drawRect(0, 0, 150, FlxG.height);
			shapes.graphics.endFill();
			this.addChild(shapes);
			shapes.alpha = .5;
			shapes.x = FlxG.width - 10;
			shapes.addEventListener(MouseEvent.ROLL_OVER, xm);
			shapes.addEventListener(MouseEvent.ROLL_OUT, ym);
			*/
			
			
			// set tutorials, need to fix later
			// also put back any in progress quests
			initializeQuestsDisplay();
			
			if (GCConstants.DEBUG_MODE)
			{
				_tutorialInstance.getNextTutorialQuests(playerData._tutorialStageAt);
				this.onAddTutorialQuests(null);
				generateNewNPCOrder();
			}
			
			// Starts the timer to gather resources from harvest buildings
			// and put them into your warehouse
			_resourceGatherTimer.start();
			
			//_salesCheckTimer.start();
			
			/*
			 * Start out game at world map always
			 */
			_mainDisplay.changeState(GCMainDisplay.AT_WORLD_MAP);
			
			/*
			 * Show an initial report screen when a player logs back in, shows them information
			 * about any sells they had since they last logged in, extra resources gathered by workers,
			 * and current quests that they have.
			 */
			if (_tutorialInstance.currentTutorialStage > GCTutorial.STAGE_VERY_START)
			{
				_salesSinceLastLogin = new Vector.<GCItem>();
				_salesWaitQueue = new Vector.<uint>();
				
				if (GCConstants.DEBUG_MODE)
				{
					displaySummaryScreen();
				}
				else
				{
					/*
					 * Wait to load all information about sales
					 */
					for (var i:uint = 0; i < GCCityData.NUM_CITIES; i++)
					{
						_salesWaitQueue.push(0);
						updateItemsSoldAt(i, true);
					}
				}
			}
			_bodyBar.addNotification("You have logged into the game!");
		}
		
		//----------------------------------------------------------------------
		//	EVENT HANDLERS
		//----------------------------------------------------------------------
		
		public function onButtonClick(e:MouseEvent):void
		{
			trace("X: "  + e.stageX + " Y: " + e.stageY);
			
		}
		public function xm(event:MouseEvent):void
		{
			s.startDrag();
			//TweenLite.to(shapes, .1, {x:FlxG.width- shapes.width, ease:Quad.easeOut});
		}
		
		public function ym(event:MouseEvent):void
		{
			s.stopDrag();
			//TweenLite.to(shapes, .1, {x:FlxG.width- 10, ease:Quad.easeOut});

		}

		override public function update():void
		{
			flash.ui.Mouse.show();
			super.update();			
		}				
				
		
		
		//----------------------------------------------------------------------
		//	DISPLAY FUNCTIONS
		//----------------------------------------------------------------------
		/** Functions to change what is being displayed **/
		
		private function displayInventory(event:Event):void
		{
			_mainDisplay.changeState(GCMainDisplay.AT_INVENTORY);
		}
		
		private function displaySynthesize(event:Event):void
		{
			//event.
			_mainDisplay.changeState(GCMainDisplay.AT_SYNTHESIZE);			
		}
		
		private function displayRepair(event:Event):void
		{
			_mainDisplay.changeState(GCMainDisplay.AT_SHIP_REPAIR);
		}
		
		private function displayMap(event:Event):void
		{
			_mainDisplay.changeState(GCMainDisplay.AT_WORLD_MAP);
		}
		
		private function displayInfo(event:Event):void
		{
		}
		
		private function onDisplayTrophyRoom(event:Event):void
		{
			_mainDisplay.changeState(GCMainDisplay.AT_TROPHY_ROOM);
		}
		
		/**
		 * On start up, we will need to properly display quests that the player had in progress
		 * since the last time they quit.
		 */
		private function initializeQuestsDisplay():void
		{
			var questsInProgress:Vector.<GCQuest> = playerData._quests;
			
			for each (var quest:GCQuest in questsInProgress)
			{
				// Check if we need to add the quest to the ui display
				if (quest.getQuestParameter(GCQuest.DISPLAYABLE) == GCQuest.TRUE)
				{
					_bodyBar.addQuest(quest);
				}
			}
		}
		
		/**
		 * When a quest has been finished, we immediately replace it with a new one.
		 * 
		 * @param	event
		 */
		private function onQuestFinished(event:GCQuestEvent):void
		{
			trace("Quest finished in playstate");
			var completedQuest:GCQuest = event.quest;
			var rewardId:uint = completedQuest.rewardId;
			var rewardAmount:uint = completedQuest.rewardAmount;
			
			if (completedQuest.questType == GCQuestData.QUEST_TYPE_NPCORDER)
			{	
				/**
				 * If quest was successfully completed, give reward to the player
				 */
				if (event.successfullyCompleted)
				{
					var xpEarned:uint = completedQuest.getNPCOrderExperience();
					var successText:String = "Customer order was successfully completed!\n\n";
					var rewardName:String = (rewardId == GCItemData.ITEM_NULL) ? "Gold" : GCItemData.getNameFromId(rewardId);
					var xpText:String = "\nXP: " + String(xpEarned);
					successText += "Reward\n" + String(rewardAmount) + " " + rewardName + xpText;
					
					
					GCPlayState.disableScreen(new GCTextPopup(successText, GCPlayState.enableScreen));
					
					GCPlayState.addNewnotification("Order successfully completed!");
					GCPlayState.increaseExperience(xpEarned);
					if (rewardId == GCItemData.ITEM_NULL)
					{
						// Rewarding player with gold
						GCPlayState.changePlayerGold(rewardAmount);
					}
					else
					{
						// Rewarding player with an item, place it in their ship's
						// inventory (okay if it exceeds capacity)
						var rewardItems:Dictionary = new Dictionary();
						rewardItems[rewardId] = rewardAmount;
						rewardPlayerItems(GCCityData.CITY_SHIP, rewardItems);
					}
				}
				else
				{
					GCPlayState.addNewnotification("Quest failed!");
				}
				
				//DATABASE CALL- indicate a quest was finished
				if (!GCConstants.DEBUG_MODE)
				{
					GCUtil.completeQuest(completedQuest.qid, 0, playerData.getPlayerId(), 0, 0, 0, 0, 0, function(code:uint, data:Object):void
					{
						trace("DATABASE-- Completed a normal order quest.");
					});
				}
				
				/*
				 * Remove the quest from the UI and player data structures
				 */
				_bodyBar.removeQuest(completedQuest);
				playerData.removeQuest(completedQuest);
				
				generateNewNPCOrder();
				
			}
			else
			{
				/*
				 * A tutorial order, probably have a special ending message.
				 */
				trace("GCPlayState-- Special tutorial order complete");
				GCPlayState.dispatchTutorialComplete(completedQuest.templateId);
			}
		}
		
		public static function dispatchTutorialComplete(id:uint):void
		{
			var quest:GCQuest = GCPlayState.tutorialSequence.getTutorialQuestFromTemplate(id);
			var questEvent:GCQuestEvent = new GCQuestEvent(GCEvent.QUEST_RESOLVED_EVENT);
			questEvent.quest = quest;
			GCPlayState.tutorialSequence.dispatchEvent(questEvent);
		}
		
		/**
		 * Triggered when one of the tutorial quests has been completed. We will
		 * also check if all the quests for a particular tutorial stage has been
		 * completed here. If they have been, we can move onto the next stage.
		 * 
		 * @param	event
		 */
		private function onTutorialQuestComplete(event:GCQuestEvent):void
		{	
			
			// Get the quest that was completed
			var completedQuest:GCQuest = event.quest;
			trace("GCPlayState-- A tutorial quest was complete " + completedQuest.templateId);
			
			if (!GCConstants.DEBUG_MODE)
			{
				//DATABASE CALL- indicate a tutorial quest was finished
				GCUtil.completeQuest(completedQuest.qid, 0, playerData.getPlayerId(), 0, 0, 0, 0, 0, function(code:uint, data:Object):void
				{
					trace("DATABASE-- Completed a tutorial quest.");
				});
			}
			
			/*
			 * Show a message indicating successful completion of a quest
			 */
			var endMessage:String = GCQuestData.getTutorialDescriptionFromId(completedQuest.templateId) + "\n";
			var xpEarned:uint = completedQuest.getQuestParameter(GCQuest.XP);
			var rewardId:uint = completedQuest.rewardId;
			var rewardAmount:uint = completedQuest.rewardAmount;
			
			if (rewardAmount > 0)
			{
				endMessage += "\nReward : " + String(rewardAmount) + " ";
				var rewardName:String = (rewardId == GCItemData.ITEM_NULL) ? "Gold" : GCItemData.getNameFromId(rewardId);
				endMessage += rewardName;
			}
			
			if (xpEarned > 0)
			{
				endMessage += "\nXP Gained: " + String(xpEarned);
			}
			
			// Special case if they fail the first timed order
			if (completedQuest.templateId == GCQuestData.QUEST_TEMPLATE_TUTORIAL_9 &&
				completedQuest.currentProgress < completedQuest.goalQuantity)
			{
				endMessage = "Sorry, you didn't finish the order in time.\n\n" + GCQuestData.getTutorialDescriptionFromId(completedQuest.templateId);
				xpEarned = 0;
				rewardAmount = 0;
			}
			
			var completionText:GCTextPopup = new GCTextPopup(endMessage, function():void
			{
				
				// Remove the quest from the list of active subgoals
				GCPlayState.tutorialSequence.removeSubgoal(completedQuest.templateId);
				GCPlayState.enableScreen();
				
				/*
				 * Remove the quest from the UI display
				 */
				if (completedQuest.getQuestParameter(GCQuest.DISPLAYABLE) == GCQuest.TRUE)
				{
					trace("Quest Removed");
					_bodyBar.removeQuest(completedQuest);
				}
				
				/*
				 * Check if all other quests in this tutorial stage are complete.
				 * If they are then we will advance to the next stage
				 */
				if (GCPlayState.tutorialSequence.numRemainingSubGoals == 0)
				{
					trace("GCPlayState-- Advancing to next stage of the tutorial!");
					if (GCConstants.DEBUG_MODE)
					{
						onPerformUpdateTutorial();
					}
					else
					{
						//DATABASE CALL- update the player's tutorial progress
						GCUtil.updateTutorialProgress(playerData.getPlayerId(), GCPlayState.tutorialSequence.currentTutorialStage + 1,
						function (code:uint, data:Object):void
						{
							onPerformUpdateTutorial();
						});
					}
					
					function onPerformUpdateTutorial():void
					{
						GCPlayState.tutorialSequence.goToNextStage();
						GCPlayState.tutorialSequence.dispatchEvent(new GCQuestEvent(GCEvent.ADD_NEW_QUESTS_EVENT));
						if (GCPlayState.tutorialSequence.currentTutorialStage == GCTutorial.STAGE_FIRST_JOURNEY)
						{
							_mainDisplay.changeState(GCMainDisplay.AT_CITY_MENU);
							_bodyBar.enableMap();
							_bodyBar.enableShip();
							_bodyBar.startBlinking(GCBodyBar.COMPONENT_ID_MAP);
						}
						else if (GCPlayState.tutorialSequence.currentTutorialStage == GCTutorial.STAGE_BUILD_GATHER)
						{
							_mainDisplay.refreshDisplay();
						}
						else if (GCPlayState.tutorialSequence.currentTutorialStage == GCTutorial.STAGE_ESTABLISH_NEW_CENTER)
						{
							_bodyBar.enableRecipesAndUpgrades();
							_bodyBar.startBlinking(GCBodyBar.COMPONENT_ID_SHIP);
							_bodyBar.startBlinking(GCBodyBar.COMPONENT_ID_SYNTHESIZE);
							_bodyBar.startBlinking(GCBodyBar.COMPONENT_ID_UPGRADE);
							
							GCPlayState.disableScreen(new GCTextPopup(GCTutorial.EXPANSION_MESSAGE, GCPlayState.enableScreen));
							
							// Last tutorial stage right now, award them a recipe for getting to this point
							var rewardRecipe:Dictionary = new Dictionary();
							rewardRecipe[GCItemData.ITEM_RECIPE_RUDDER] = 1;
							rewardPlayerItems(GCCityData.CITY_SHIP, rewardRecipe);
						}
					}
				}
				
				/*
				 * Check whether to trigger the start of new customer orders. After a while
				 * it should regenerate these new orders.
				 */
				if (completedQuest.templateId == GCQuestData.QUEST_TEMPLATE_TUTORIAL_9)
				{
					generateNewNPCOrder();
					generateNewNPCOrder();
				}
				
				/*
				 * Give out the rewards for completing the quest
				 */
				if (rewardId == GCItemData.ITEM_NULL && rewardAmount > 0)
				{
					trace("Rewarded gold");
					GCPlayState.changePlayerGold(rewardAmount);
				}
				else if (rewardId != GCItemData.ITEM_NULL)
				{
					// Add the different type of reward to the their inventory
					var rewardItems:Dictionary = new Dictionary();
					rewardItems[rewardId] = rewardAmount;
					rewardPlayerItems(GCCityData.CITY_SHIP, rewardItems);
				}
				 
				
				playerData.removeQuest(completedQuest);
				GCPlayState.increaseExperience(xpEarned);
			});
			GCPlayState.disableScreen(completionText);
			
			
		}
		
		private function generateNewNPCOrder():void
		{
			var newQuest:GCQuest;
			
			// Give the player a very easy quest if they are short on gold
			if (GCPlayState.playerData.gold < 300)
			{
				trace("Give the player an easy quest");
				newQuest = GCQuest.generateEasyQuest();
				
				// If player is unable to finish even that quest, then we simply give them
				// some gold and some food
				if (newQuest == null)
				{
					var helpGivenText:String = "Its looks like you're short on supplies and gold! The locals have lent you some gold and items to help " +
									"you get back on your feet.";
					GCPlayState.disableScreen(new GCTextPopup(helpGivenText, function():void
					{
						GCPlayState.changePlayerGold(300);
						
						var helpItems:Dictionary = new Dictionary();
						helpItems[GCItemData.ITEM_FOOD] = 10;
						GCPlayState.rewardPlayerItems(GCCityData.CITY_SHIP, helpItems);
						GCPlayState.enableScreen();
						
						// Generate a new random quest
						newQuest = GCQuest.generateQuest();
					}));
				}
				else
				{
					var easyQuestText:String = "It looks like you're running low on gold! Look at your tasks there is a new order where you can earn some more!";
					GCPlayState.disableScreen(new GCTextPopup(easyQuestText, GCPlayState.enableScreen));
				}
			}
			else
			{
				newQuest = GCQuest.generateQuest();
			}
			
			if (GCConstants.DEBUG_MODE)
			{
				performQuestAdd(0);
			}
			else
			{
				// DATABASE CALL- add a new order quest
				GCUtil.assignNewQuest(playerData.getPlayerId(), newQuest.timeRemaining, newQuest.templateId, newQuest.assignerId,
					newQuest.locationId, newQuest.goalId, newQuest.goalQuantity, newQuest.rewardId, newQuest.rewardAmount,
					function (code:uint, data:Object):void
					{
						trace("DATABASE-- confirmed addition of a new quest");
						var qid:uint = data["id"];
						performQuestAdd(qid);
					});
			}
			
			// Add the quests to the ui and logical data structures
			function performQuestAdd(qid:uint):void
			{
				newQuest.qid = qid;
				playerData.addQuest(newQuest);
				_bodyBar.addQuest(newQuest);
			}
		}
		
		private function onAddTutorialQuests(event:GCQuestEvent):void
		{
			trace("GCPlayState-- Adding new tutorials");
			var newQuestsList:Dictionary = GCPlayState.tutorialSequence.tutorialSubGoals;
			
			// Iterate through each quest
			for each (var quest:GCQuest in newQuestsList)
			{				
				if (_bodyBar.getQuestsList()[quest] != null)
				{
					trace("trying to add quest that already exists");
					continue;
				}
	
				onAddQuest(quest);
			}
			
			// Nested function that will add the quest to the game and ui after getting
			// back the needed id for that quest
			function onAddQuest(quest:GCQuest):void
			{
				if (GCConstants.DEBUG_MODE)
				{
					onPerformTutorialAdd(0);
				}
				else
				{
					//DATABASE CALL -- assign the tutorial quests
					GCUtil.assignNewQuest(playerData.getPlayerId(), quest.timeRemaining, quest.templateId, quest.assignerId,
						quest.locationId, quest.goalId, quest.goalQuantity, quest.rewardId, quest.rewardAmount, function(code:uint, data:Object):void
					{
						trace("DATABASE-- recieved back quest added confirmation");
						var qid:uint = data["id"];
						onPerformTutorialAdd(qid);
					});
				}
				
				function onPerformTutorialAdd(qid:uint):void
				{
					quest.qid = qid;

					if (quest.getQuestParameter(GCQuest.DISPLAYABLE) == GCQuest.TRUE)
					{
						_bodyBar.addQuest(quest);
					}
					playerData.addQuest(quest);
				}
			}
		}
		
		//----------------------------------------------------------------------
		//	???
		//----------------------------------------------------------------------
		
		
		/**
		 * Function that is triggered on every specified interval to have workers
		 * gather some resources.
		 * 
		 * @param	event
		 */
		private function onGatherResource(event:TimerEvent):void
		{
			getGatheredResourceAmount(1);
		}
		
		/**
		 * Gathers resources from each city, putting contents into the warehouse. Type and amount
		 * depend on the building and workers at that building
		 * 
		 * @param	resourceModifier
		 * @return
		 */
		private function getGatheredResourceAmount(resourceModifier:uint):Dictionary
		{
			/*
			* Iterate through all cities in the game.
			*/
			var wereResourcedGathered:Boolean = false;
			
			// Key:location id
			// Value: Dictionary(which further contain itemid and quantity to add pairs)
			var resourcesGathered:Dictionary = new Dictionary();
			for each (var city:GCCity in _mapData._cities)
			{
				resourcesGathered[city.getId()] = new Dictionary();
				/*
				* Look through all constructed buildings and check for ones that are of
				* type harvest buildings. Then grab the resource associated with that building
				* and the gathering rate for that building. Add the appropriate amount of resources
				* to the player's city inventory.
				*/
				for each (var building:GCCityBuilding in city.getConstructedBuildings())
				{
					var warehouse:GCInventory = city.getInventoryFromCity();
					
					/*
					* Check that there is actually a warehouse to put the goods into. Also make sure the
					* building we are looking at in a harvest type and the rate of production is greater
					* than zero.
					*/
					if (GCBuildingData.getBuildingTypeFromId(building.buildingId) == GCBuildingData.BUILDING_TYPE_HARVEST &&
						warehouse != null && building.getRate() > 0)
					{
						var resourceId:uint = building.getHarvestableResource();
						var amountToAdd:uint = building.getRate() * resourceModifier;
						
						// Capacity check
						if (warehouse.currentCapcity + amountToAdd > warehouse.maxCapacity)
						{
							amountToAdd = warehouse.maxCapacity - warehouse.currentCapcity;
							
							// If can't add anymore, move to the next city
							if (amountToAdd == 0)
							{
								GCPlayState.addNewnotification("Your warehouse in " + city.getName() + " is full!");
								break;
							}
						}
						// Add the items to the warehouse
						var goodsToAdd:Dictionary = new Dictionary();
						goodsToAdd[resourceId] = amountToAdd;
						(resourcesGathered[city.getId()] as Dictionary)[resourceId] = amountToAdd;
						GCPlayState.rewardPlayerItems(city.getId(), goodsToAdd);
						
						wereResourcedGathered = true;
						
						/**
						 * Roy-- tutorial
						 * Checking if they have gathered enough of a certain resource from their
						 * harvest building.
						 */
						if (GCPlayState.tutorialSequence.currentTutorialStage == GCTutorial.STAGE_BUILD_GATHER)
						{
							var template:uint = GCQuestData.QUEST_TEMPLATE_TUTORIAL_17;
							var quest:GCQuest = GCPlayState.tutorialSequence.getTutorialQuestFromTemplate(template);
							
							// Do the last check so we only trigger the quest complete once
							if (quest != null && quest.goalId == resourceId && quest.currentProgress < quest.goalQuantity)
							{
								quest.currentProgress += amountToAdd;
								if (quest.currentProgress >= quest.goalQuantity)
								{
									GCPlayState.dispatchTutorialComplete(template);
								}
							}
						}
					}
				}
			}
			if (wereResourcedGathered)
			{
				GCPlayState.addNewnotification("Workers have gathered resources!");
			}
			
			return resourcesGathered;
		}
		
		/**
		 * Get inventory from your ship
		 * 
		 * @return
		 */
		public static function getPlayerInventory():GCInventory
		{
			return playerData.shipInventory;
		}
		
		public static function addShip(newShip:GCShip):void
		{	
			playerData._playerAllShips.push(newShip);
		}
		
		public static function changeActiveShip(newShip:GCShip):void
		{
			playerData._playerActiveShip = newShip;
		}
		
		/**
		 * Get the current ship being used by the player
		 * @return
		 */
		public static function getCurrentShip():GCShip
		{
			return playerData.currentShip;
		}
		
		/**
		 * Function to get the actual city information based
		 * on a city id
		 * 
		 * @param	cityID
		 * @return The actual city object.
		 */
		public static function getCityData(cityID:int):GCCity 
		{
			return _mapData._cities[cityID];
		}
		
		/**
		 * Function to get the city object where the player is docked at.
		 * 
		 * @return The city object of the location the player is docked at.
		 */
		public static function getCurrentCity():GCCity
		{
			return getCityData(playerData.shipLocation);
		}
		
		/**
		 * Function to set a new location of where the player is current docked
		 * at. 
		 * 
		 * @param	newCityId
		 */
		public static function setNewPlayerLocation(newCityId:int):void
		{	
			playerData.shipLocation = newCityId;
			_infoBar.updateLocation(GCCityData.getNameFromId(newCityId));
			
			if (!GCConstants.DEBUG_MODE)
			{
				//DATABASE CALL- update player location
				GCUtil.changePlayerLocation(GCPlayState.playerData.getPlayerId(), newCityId, function(code:uint, data:Object):void
				{
					trace("DATABASE-- changed player's location");
				});
			}
		}
		
		public static function getPlayerGold():uint
		{
			return playerData.gold;
		}
		
		public static function changePlayerGold(delta:int):void
		{
			playerData.gold += delta;
			_infoBar.updateGold(playerData.gold);
			
			if (!GCConstants.DEBUG_MODE)
			{
				//DATABASE CALL- update player's total gold
				GCUtil.updateGold(playerData.getPlayerId(), playerData.gold, function(code:uint, data:Object):void
				{
					trace("DATABASE-- changed player's gold");
				});
			}
		}
		
		public static function getPlayerId():uint
		{
			return playerData.getPlayerId();
		}
		
		public static function getPlayerStats():Vector.<uint>
		{
			return playerData._playerActiveShip._stats;
		}
		
		/**
		 * Functions to get and set the number of workers who are onboard the
		 * player's current ship.
		 */
		public static function getWorkersOnShip():uint
		{
			return playerData.shipWorkers;
		}
		
		public static function setWorkersOnShip(newNumWorkers:uint):void
		{
			playerData.shipWorkers = newNumWorkers;
		}
		
		/**
		 * Function to award experience to the user. Will update the ui component as well
		 * as the actual player data.
		 * 
		 * @param	amountToGive
		 * 		Amount of experience to add to the player
		 */
		public static function increaseExperience(amountToGive:uint):void
		{
			playerData.totalXp += amountToGive;
			playerData.lvlXp += amountToGive;
			if (playerData.lvlXp > GCExperienceData._experienceSequence[playerData.playerLevel])
			{
				trace("Leveled Up!!!!");
				playerData.lvlXp -= GCExperienceData._experienceSequence[playerData.playerLevel];
				playerData.playerLevel++;

				if (playerData.playerLevel == 2)
				{
					/*
					 * At level 2, player unlocks the ability to use thier ship.
					 * The world map button and ship inventory button are unlocked for use.
					 */
					GCPlayState.disableScreen(new GCTextPopup(GCExperienceData.EXP_FIRST_LEVEL_UP, GCPlayState.enableScreen));
				}
				else if (playerData.playerLevel == 3)
				{
					GCPlayState.disableScreen(new GCTextPopup(GCExperienceData.EXP_SECOND_LEVEL_UP, GCPlayState.enableScreen));
					
					/*
					 * Rewarded with a batch of goods in Picaroon warehouse
					 */
					var rewardGoodsIds:Dictionary = new Dictionary();
					rewardGoodsIds[GCItemData.ITEM_IRON] = 15;
					rewardGoodsIds[GCItemData.ITEM_LUMBER] = 15;
					rewardGoodsIds[GCItemData.ITEM_FOOD] = 15;
					rewardPlayerItems(GCCityData.CITY_PICAROON, rewardGoodsIds);
					
				}
				else if (playerData.playerLevel == 4)
				{
					if (GCPlayState.tutorialSequence.getTutorialQuestFromTemplate(GCQuestData.QUEST_TEMPLATE_TUTORIAL_11) != null)
					{
						GCPlayState.dispatchTutorialComplete(GCQuestData.QUEST_TEMPLATE_TUTORIAL_11);
					}
					
					// Reward the player with three extra workers for leveling up, found in Picaroon
					for (var i:uint = 0; i < 3; i++)
					{
						if (!GCConstants.DEBUG_MODE)
						{
							GCUtil.addCrew(playerData.getPlayerId(), GCCityData.CITY_PICAROON, GCBuildingData.BUILDING_NONE, function(code:uint, data:Object):void
							{	
							});
						}
						GCPlayState.getCityData(GCCityData.CITY_PICAROON).idleWorkers = 3;
						GCPlayState.getCityData(GCCityData.CITY_PICAROON).totalWorkers = 3;
					}
				}
				else
				{
					/*
					 * Give a message indicating they have leveled up.
					 * Possibly assign a reward here as well.
					 */
					GCPlayState.disableScreen(new GCTextPopup(GCExperienceData.EXP_GENERAL_LEVEL_UP, GCPlayState.enableScreen));
					
					if (GCConstants.DEBUG_MODE)
					{
						GCPlayState.setWorkersOnShip(GCPlayState.getWorkersOnShip() + 1);
					}
					else
					{
						//DATABASE CALL- give player a worker as a reward for leveling up
						GCUtil.addCrew(playerData.getPlayerId(), GCCityData.CITY_SHIP, GCBuildingData.BUILDING_NONE, function(code:uint, data:Object):void
						{
							trace("DATABASE-- Awarded extra worker on ship");
							GCPlayState.setWorkersOnShip(GCPlayState.getWorkersOnShip() + 1);
						});
					}
					
					GCExperienceData.giveExperienceReward();
				}
			}

			if (!GCConstants.DEBUG_MODE)
			{
				//DATABASE CALL- update total experience
				GCUtil.updatePlayerXp(playerData.getPlayerId(), playerData.totalXp, function(id:uint, obj:Object):void
				{
					trace("DATABASE-- changed player's xp");
				});
			}
			_infoBar.updateExperience(playerData.playerLevel, playerData.lvlXp);
		}
		
		/**
		 * Function to reward the player with items at a specified location
		 * 
		 * @param	lid
		 * 		Location where to put reward
		 * @param	rewardItems
		 * 		Dictionary key:itemId, value:quantity to reward
		 */
		public static function rewardPlayerItems(lid:uint, rewardItems:Dictionary):void
		{
			// Iterate through all the reward items
			for (var obj:Object in rewardItems)
			{
				var rewardId:uint = obj as uint;
				var rewardAmount:uint = rewardItems[rewardId];
				
				onAddReward(rewardId, rewardAmount);
			}
			
			// Add the actual reward to the player's inventory and save to database
			function onAddReward(rewardId:uint, rewardAmount:uint):void
			{
				if (GCConstants.DEBUG_MODE)
				{
					var inventoryToAddTo:GCInventory = (lid == GCCityData.CITY_SHIP) ? GCPlayState.getPlayerInventory() : GCPlayState.getCityData(lid).getInventoryFromCity();
					inventoryToAddTo.addQuantityToInventory(0, rewardId, rewardAmount);
				}
				else
				{
					//DATABASE CALL- reward the player with an item
					GCUtil.addToInventory(GCPlayState.getPlayerId(), rewardId, lid, rewardAmount, function(code:uint, data:Object):void
					{
						var invItemId:uint = data["id"];
						
						var inventoryToAddTo:GCInventory = (lid == GCCityData.CITY_SHIP) ? GCPlayState.getPlayerInventory() : GCPlayState.getCityData(lid).getInventoryFromCity();
						inventoryToAddTo.addQuantityToInventory(invItemId, rewardId, rewardAmount);
					});
				}
			}
		}
		
		/**
		 * Function to check if one of their items thay have put on sale was bought
		 * by another player while they are still logged in. Function will need to be called
		 * periodically by a timer.
		 */
		private function onCheckPlayerSales(event:TimerEvent):void
		{
			for (var i:uint = 0; i < GCCityData.NUM_CITIES; i++)
			{
				updateItemsSoldAt(i);
			}
		}
		
		/**
		 * Updates player sales
		 * 
		 * @param	lid
		 * @param	waiting
		 * 			Whether we are waiting for all information to return (true only for fetching data for game summary)
		 */
		private function updateItemsSoldAt(lid:uint, waiting:Boolean = false):void
		{
			GCUtil.getPlayerItemsForSaleAt(GCPlayState.getPlayerId(), lid, function(code:uint, data:Object):void
			{
				var playerSales:Object = data["details"];
				var currentSalesAt:GCInventory = GCPlayState.getCityData(lid).getSalesFromCity();
				if (currentSalesAt == null)
				{
					return;
				}
				
				// Key:itemId, Value:Quantity on Sale
				var salesOnDB:Dictionary = new Dictionary();
				
				// Check what the sales inventory at a city looks like from the DB
				for each (var item:Object in playerSales)
				{
					var invItemId:uint = item["id"];
					var itemId:uint = item["itemId"];
					var saleQuantity:uint = item["saleQty"];
					var pricePer:uint = item["salePrice"];
					
					salesOnDB[itemId] = saleQuantity;
				}
				
				// Compare the DB's view of sales to the front-end, check differences
				// If there are any then, then we had a sale.
				for each (var saleItem:GCItem in currentSalesAt.inventory)
				{
					var saleItemId:uint = saleItem.itemId;
					var amountBought:uint = 0;
					if (salesOnDB[saleItemId] == null)
					{
						// Someone bought all the items of a sale
						amountBought = saleItem.quantity;
					}
					else if (salesOnDB[saleItemId] < saleItem.quantity)
					{
						// Someone bought some of an item on sale, update the sale on the front end
						// along with the gold display
						amountBought = saleItem.quantity - salesOnDB[saleItemId]
					}
					
					if (amountBought > 0)
					{
						trace("Someone bought your stuff");
						
						// update the sale on the front end along with the gold display
						_infoBar.updateGold(playerData.gold + amountBought * saleItem.price);
						currentSalesAt.removeFromInventory(saleItemId, amountBought);
						GCPlayState.increaseExperience(amountBought * saleItem.price);
						var text:String = "Successfully sold " + String(amountBought) + " " + GCItemData.getNameFromId(saleItemId) +
							"for " + String(saleItem.price) + "gold at " +GCCityData.getNameFromId(lid);
						GCPlayState.addNewnotification(text);
					}
				}
				
				if (waiting)
				{
					_salesWaitQueue.pop();
					if (_salesWaitQueue.length == 0)
					{
						// Done loading all possible sales for the summary
						displaySummaryScreen();
					}
				}
			});
		}
		
		/**
		 * Display a summary screen when a player returns.
		 */
		private function displaySummaryScreen():void
		{
			var summaryText:String = "Welcome Back! While you were away:\n";
			
			/*
			 * Get information about the player sale
			 */
			for each (var soldItem:GCItem in _salesSinceLastLogin)
			{
				// Display info about this sale
			}
			 
			/*
			 * Display information about bonus resources gathered by workers
			 */
			var resourceReturnBonus:Dictionary = getGatheredResourceAmount(3);
			var totalResourceGainedText:String = "Resources Gathered By Workers:";
			for (var obj:Object in resourceReturnBonus)
			{
				var lid:uint = obj as uint;
				var resourceList:Dictionary = resourceReturnBonus[lid];
				
				var resourceGainedAtText:String = "";
				for (var objType:Object in resourceList)
				{
					if (resourceGainedAtText == "")
					{
						resourceGainedAtText = GCCityData.getNameFromId(lid) + ":";
					}
					
					var itemId:uint = objType as uint;
					var quantity:uint = resourceList[itemId];
					var bonusText:String = "\t+" + String(quantity) + " " + GCItemData.getNameFromId(itemId);
					trace(bonusText);
				}
				if (resourceGainedAtText != "")
				{
					totalResourceGainedText += "\n" + resourceGainedAtText;
				}
			}
			summaryText += totalResourceGainedText + "\n\n";
			
			/*
			 * Display information about current quests that the player still needs to perform
			 */
			trace("Total tasks currently in progress " + playerData.getQuests().length);
			var allOrdersText:String = "";
			for each (var quest:GCQuest in playerData.getQuests())
			{
				trace("SUMMARY-- display a quest");
				if (quest.questType == GCQuestData.QUEST_TYPE_NPCORDER)
				{
					var progress:String = String(quest.currentProgress);
					var required:String = String(quest.goalQuantity);
					var itemRequested:String = GCItemData.getNameFromId(quest.goalId);
					var location:String = GCCityData.getNameFromId(quest.locationId);
					var timeRemaining:String = String(quest.timeRemaining);
					var questInformation:String = "Order at " + location +
								"\nProgress " + progress + "/" + required + " " + itemRequested +
								"\nTime Left " + timeRemaining + " minutes";
					allOrdersText += (questInformation + "\n\n"); 
				}
			}
			summaryText += allOrdersText;
			
			GCPlayState.disableScreen(new GCTextPopup(summaryText, function():void
			{
				GCPlayState.enableScreen();
			}, 300, 500));
		}
		
		/**
		 * Function to get the current tutorial sequence.
		 */
		public static function get tutorialSequence():GCTutorial
		{
			return _tutorialInstance;
		}
		
		/**
		 * Function to add a new notification to the notice box.
		 * 
		 * @param	info
		 * 		The new notice to append to the notice area
		 */
		public static function addNewnotification(info:String):void
		{
			_playStateInstance._bodyBar.addNotification(info);
		}
		
		/**
		 * Function to disable the game screen, placing an optional sprite centered in
		 * the screen.
		 * 
		 * @param	extraComponent
		 */
		public static function disableScreen(extraComponent:Sprite = null):void
		{
			/*
			 * If screen was already disabled, then put new request on a queue to be
			 * displayed at the next available instance in time.
			 */
			if (_coveringSprite.parent != null)
			{
				_disableWaitQueue.push(extraComponent);
				return;
			}
			
			if (extraComponent != null)
			{
				extraComponent.x = FlxG.width / 2 - extraComponent.width / 2;
				extraComponent.y = FlxG.height / 2 - extraComponent.height / 2;
				_coveringSprite.addChild(extraComponent);
			}
			trace("disabled screen");
			_playStateInstance.addChild(_coveringSprite);
		}
		
		/**
		 * Function that reenables the screen.
		 */
		public static function enableScreen():void
		{
			if (_coveringSprite.parent != null)
			{
				trace("enabled screen");
				_playStateInstance.removeChild(_coveringSprite);
			}
			
			while (_coveringSprite.numChildren > 0)
			{
				_coveringSprite.removeChildAt(0);
			}
			
			/*
			 * If other components were waiting to be displayed, remove them
			 * from the queue and paste them on the screen now.
			 */
			if (_disableWaitQueue.length > 0)
			{
				var component:Sprite = _disableWaitQueue.shift();
				GCPlayState.disableScreen(component);
			}
		}	
	}
}